/*
 * Copyright 2008 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package blms.domain;

import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import blms.exception.UnknownVariableException;
import blms.util.BLMSMessages;

/**
 * <code>Joining</code> is a abstraction to the joining process in a billiard
 * league. The joining process includes assign a initial handicap to the new user.
 * Handicap is a way to enable players with different habilities playing together.
 * 
 * @author João Arthur
 * @author Jonhnny Weslley
 * @author Thiago Emmanuel
 * @version 1.00, 22/04/2008
 * @since 1.0
 */
public class Joining extends Entity {

	private static final long serialVersionUID = -1444913980179243427L;

	private User user;
	private League league; // REMOVE LEAGUE
	private Date initialDate;
	private int initialHandicap;

	private List<ManualHandicapInsertion> handicapInsertions;
	
	/**
	 * Default Constructor for Joining.
	 */
	public Joining() {
		// Default constructor
	}

	/**
	 * Constructs a new Joining.
	 * 
	 * @param user
	 *            the user joining the league.
	 * @param league
	 *            the league which the user will join.
	 * @param joiningDate
	 *			  the user joining date	 			
	 * @param initialHandicap
	 * 			  user initial handicap		
	 */
	public Joining(final User user, final League league, Date joiningDate, int initialHandicap) {
		this.user = user;
		this.league = league;
		this.initialDate = joiningDate;
		this.initialHandicap = initialHandicap;
		this.handicapInsertions = new LinkedList<ManualHandicapInsertion>();
	}

	/**
	 * Returns the user of this <code>Joining</code>.
	 * 
	 * @return the user of this <code>Joining</code>.
	 */
	public User getUser() {
		return user;
	}

	/**
	 * Returns the league of this <code>Joining</code>.
	 * 
	 * @return the league of this <code>Joining</code>.
	 */
	public League getLeague() {
		return league;
	}

	/**
	 * Returns the date when this <code>Joining</code> was done.
	 * 
	 * @return the date when this <code>Joining</code> was done.
	 */
	public Date getInitialDate() {
		return initialDate;
	}

	/**
	 * Returns the initial handicap of the user in this <code>Joining</code>.
	 * 
	 * @return the initial handicap of the user in this <code>Joining</code>.
	 */
	public int getInitialHandicap() {
		return initialHandicap;
	}

	/**
	 * Returns the current handicap of the user in this <code>Joining</code>.
	 * 
	 * @return the current handicap of the user in this <code>Joining</code>.
	 */
	public int getCurrentHandicap() {
		
		// FIXME: o nosso codigo esta muito macarronico, isto eh consequencia de objetos mal-pensados.
		// de inicio o joining parecia ser uma entidade estatica, agora ele eh dinamico.
		// embora eu ache que deve existir uma entidade dinamica que cuida das coisas relacionadas
		// aos usuario na liga, acho que nao pode ser do jeito que esta sendo feito em joining.
		// de qq modo nao precisara de muito refactoring
		
		// FIXME: esse leaguecontext precisa da league e do user ? 
		
		int baseHandicapValue;
		List<Match> matchesFromUser;
		Expression handicapExpression = getLeague().getHandicapExpression();
		
		if(wasDefinedAManualHandicap()) {
			ManualHandicapInsertion manualHandicap = getManualHandicap();
			baseHandicapValue = manualHandicap.getHandicap();
			Date insertionDate = manualHandicap.getInsertionDate();
			matchesFromUser = league.getMatchesFrom(user, insertionDate);
		}
		else {
			baseHandicapValue = getInitialHandicap();
			matchesFromUser = league.getMatchesFrom(getUser());
		}
		
		return defineExpressionValue(handicapExpression, baseHandicapValue, matchesFromUser);
	}

	private ManualHandicapInsertion getManualHandicap() {
		return handicapInsertions.get(handicapInsertions.size() - 1);
	}

	private boolean wasDefinedAManualHandicap() {
		return !handicapInsertions.isEmpty();
	}

	private int defineExpressionValue(Expression handicapExpression, int baseHandicapValue, List<Match> matchesFromUser) {
		
		int handicapValue = baseHandicapValue;
		
		if (handicapExpression == null) return handicapValue;
		
		for (Match match : matchesFromUser) {
			
			int delta;

			try {
				delta = handicapValue + handicapExpression.evaluate(new HandicapContext(match, user));
				handicapValue = Math.max(0, delta);
			} catch (UnknownVariableException e) {
				throw new UnknownVariableException(BLMSMessages.UNKNOWN_HANDICAP_VARIABLE);
			} catch (ArithmeticException e) {
				throw new ArithmeticException(BLMSMessages.DIVISION_BY_ZERO_IN_HANDICAP);
			}
		}
		
		return handicapValue;
	}

	public void setPlayerHandicap(Date insertionDate, int handicap) {
		addManualHandicapInsertion(insertionDate, handicap);
	}
	
	private void addManualHandicapInsertion(Date insertionDate, int handicap) {
		handicapInsertions.add(new ManualHandicapInsertion(insertionDate, handicap));
	}

	/**
	 * @return
	 */
	public int getHandicapHistorySize() {
		
		int manualInsertions = handicapInsertions.size();
		int changesByMatches = 0;
		
		if(handicapInsertions.isEmpty()) {
			changesByMatches = league.getMatchesFrom(user).size(); 
		}else {
			ManualHandicapInsertion manualHandicap = getManualHandicap(); 
			changesByMatches = league.getMatchesFrom(user, manualHandicap.getInsertionDate()).size();
		}
		
		return 1 + manualInsertions + changesByMatches;
	}
	
	/**
	 * @return
	 */
	public Date getHandicapHistoryLastUpdate() {
		
		List<Match> changesByMatches;
		
		if(handicapInsertions.isEmpty()) {
			changesByMatches = league.getMatchesFrom(user);
		}else {
			ManualHandicapInsertion manualHandicap = getManualHandicap(); 
			changesByMatches = league.getMatchesFrom(user, manualHandicap.getInsertionDate());
		}
		
		return changesByMatches.get(changesByMatches.size() -1).getMatchDate();
	}
	
	private class ManualHandicapInsertion {
	
		private final Date insertionDate;
		private final int handicap;
		/**
		 * @param insertionDate
		 * @param handicap
		 */
		public ManualHandicapInsertion(final Date insertionDate, final int handicap) {
			this.insertionDate = insertionDate;
			this.handicap = handicap;
		}
		/**
		 * @return the handicap
		 */
		public int getHandicap() {
			return handicap;
		}
		/**
		 * @return the insertionDate
		 */
		public Date getInsertionDate() {
			return insertionDate;
		}
	}
	
}